import json
import os
from dataclasses import dataclass
from typing import Tuple, Union

from flask import current_app, has_request_context, request
from flask_restx import abort

from app.common.base_manager import BaseManager
from app.common.libs import auth
from app.models import (
    Courseware,
    CoursewareType,
    Ctf,
    CtfRedBlue,
    LinkType,
    PresetRoleChoices,
    RangeLinkModule,
    RangeVm,
    User,
)
from tools import db, redis_conn


class PrivateRangeException(Exception):
    """私有range异常"""

    def __init__(self, msg):
        self.msg = msg


@dataclass(frozen=True)
class VirtualUser:
    """虚拟机用户，兼容无法获取当前用户情况"""

    id: int = 0
    username: str = "virtual_user"
    account_name: str = "virtual_user"
    is_superuser: int = 0


LinkReturnType = Tuple[Union[LinkType, None], Union[Ctf, CtfRedBlue, Courseware, None]]


class RangeManager(BaseManager):
    def __init__(
        self,
        range_id: int = None,
        range_uuid: str = None,
        ignore_private: bool = False,
        **kwargs,
    ):
        super().__init__()

        # 允许延迟加载
        self.range_model: Union[None, RangeLinkModule] = None
        self.link_type: Union[None, LinkType] = None
        self.link_obj: Union[None, Ctf, CtfRedBlue, Courseware] = None

        if any([range_id, range_uuid]):
            self.init(range_id, range_uuid, ignore_private, **kwargs)

    @classmethod
    def get_login_info(cls, **kwargs):
        """按当前上下文环境使用不同方式加载登录信息"""
        # 处于正常接口环境中
        if has_request_context():
            return auth.current_user(), request.cookies.get("role", None)
        # 处于celery环境中
        elif os.environ.get("FLASK_CONTEXT_IN_CELERY", None) == "true":
            context_info = redis_conn.get(
                f"TASK_DUMP_REQUEST_CONTEXT::{kwargs['task_id']}"
            )
            if not context_info:
                return VirtualUser(), None
            context_info = json.loads(context_info.decode())
            user = User.query.get(context_info["user_id"]) or VirtualUser()
            role = context_info["role"] or None
            return user, role
        # FIXME 命令环境或其他情况下可能需要重新适配
        else:
            return VirtualUser(), None

    def init(
        self,
        range_id: int = None,
        range_uuid: str = None,
        ignore_private: bool = False,
        **kwargs,
    ):
        """初始化目标靶场
        :param int range_id: 靶场id
        :param str range_uuid: 靶场range_uuid
        :param bool ignore_private: 忽略私有性校验，如管理员或学生端检查操作
        """
        if range_uuid:
            self.range_model: RangeLinkModule = RangeLinkModule.query.filter(
                RangeLinkModule.range_uuid == range_uuid
            ).first()
        elif range_id:
            self.range_model: RangeLinkModule = RangeLinkModule.query.filter(
                RangeLinkModule.range_id == range_id
            ).first()

        if not self.range_model:
            abort(400, message="无法查询到该靶场信息")

        current_user, current_role = self.get_login_info(**kwargs)
        is_admin = (
            current_user.is_superuser == 1 and current_role == PresetRoleChoices.admin
        )

        allow_system_range = self.range_model.Range.is_system and (
            is_admin or current_role == PresetRoleChoices.teacher
        )
        self.link_type, self.link_obj = self.get_linked_info()

        # 私有性校验
        if (
            not self.range_model.Range.is_public
            and self.range_model.user_id != current_user.id
        ):
            # 管理员/忽略私有校验/允许系统靶场访问均跳过私有校验
            if is_admin or ignore_private or allow_system_range:
                return
            # 导调裁判允许访问对应比赛场景
            elif self.link_type == LinkType.ctf_red_blue and (
                self.link_obj.rb_pilot_id == current_user.id
                or self.link_obj.rb_referee_id == current_user.id
            ):
                return
            else:
                current_app.logger.warning(f"{current_user.account_name}试图访问私有靶场")
                raise PrivateRangeException("当前用户无权访问场景环境")

    def get_linked_info(self) -> LinkReturnType:
        """获取已关联某靶场数据"""
        range_uuid = self.range_model.range_uuid

        # 检查红蓝对抗关联
        ctf_rb = CtfRedBlue.query.filter(
            CtfRedBlue.range_uuid == range_uuid,
        ).first()
        if ctf_rb:
            return LinkType.ctf_red_blue, ctf_rb

        # 检查ctf关联
        ctf = Ctf.query.filter(Ctf.range_uuid == range_uuid).first()
        if ctf:
            return LinkType.ctf, ctf

        # 检查课程关联
        courseware = Courseware.query.filter_by(
            range_uuid=range_uuid, type=CoursewareType.vm
        ).first()
        return (LinkType.courseware, courseware) if courseware else (None, None)

    def get_processing_link_info(self) -> LinkReturnType:
        """获取该场景绑定的比赛/课程中处于开启状态的部分"""
        _type, obj = self.get_linked_info()
        if not _type:
            return None, None
        if _type == LinkType.courseware:
            link_check = db.session.execute(
                f"""
                    SELECT COUNT(*) FROM `api_chapter_courseware` AS `a`
                    WHERE `a`.`courseware_id` = {obj.id} and `a`.`env_status` = '1'
                    """
            )
            if link_check.scalar() == 0:
                return None, None
        elif obj.status != 1:
            return None, None

        return _type, obj

    def get_vm_from_this(self, vm_id=None, vm_uuid=None) -> RangeVm:
        """从当前靶场中获取虚拟机信息"""
        if not vm_id and not vm_uuid:
            raise TypeError("缺少必要参数")

        _filter = [
            RangeVm.range_uuid == self.range_model.range_uuid,
            RangeVm.id == vm_id if vm_id else RangeVm.vm_uuid == vm_uuid,
        ]

        vm: RangeVm = RangeVm.query.filter(*_filter).first()
        if not vm:
            abort(400, message="虚拟机不存在")

        return vm

    def get_rang_vm_by_uuid(self):
        """
        查询某场景下所有的vm/docker
        """
        _filter = [
            RangeVm.range_uuid == self.range_model.range_uuid
        ]
        return RangeVm.query.filter(*_filter).all()